{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Прогнозирование расхода топлива автомобиля__\n",
    "\n",
    "by Kornievskaya Anastasia Andreevna"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__1. Описание набора данных и признаков __\n",
    "\n",
    "В данном проекте рассматривается задача прогнозирования расхода топлива по городу для автомобилей по их параметрам. Данные по авто были нагло стырены с интернета. Данный прогноз можно использовать для предсказания расхода для вновь выпускаемых автомобилей в процессе их проектирования с целью оптимизации (если захочется такое прогнозировать)\n",
    "\n",
    "__Список переменных:__\n",
    "\n",
    "* Make - производитель автомобиля\n",
    "* Model - модель автомобиля\n",
    "* Type - тип кузова\n",
    "* Origin - страна производитель\n",
    "* DriveTrain - привод автомобиля\n",
    "* MSRP - екомендованная производителем розничная цена\n",
    "* Invoice - стоимость автомобиля в салонах\n",
    "* EngineSize - бъем двигателя\n",
    "* Cylinders - число цилидров для цилиндрового двигателя (для ротерного стоит '.')\n",
    "* Horsepower - лошадиные силы\n",
    "* Weight - вес автомобиля\n",
    "* Wheelbase - колесная база (длина между колесами)\n",
    "* Length - длина кузова\n",
    "* **MPG_City** - расход топлива в городе (целевая переменная)\n",
    "* MPG_Highway - расход топлива на трассе\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "%matplotlib inline\n",
    "from matplotlib import pyplot as plt\n",
    "import seaborn as sns\n",
    "import os\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from scipy.sparse import csr_matrix, hstack\n",
    "from sklearn.preprocessing import StandardScaler, OneHotEncoder\n",
    "from sklearn.model_selection import StratifiedKFold\n",
    "from sklearn.metrics import roc_auc_score\n",
    "from sklearn.linear_model import LogisticRegression, LogisticRegressionCV, LinearRegression, LassoCV\n",
    "from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer, TfidfTransformer\n",
    "from sklearn.tree import DecisionTreeClassifier\n",
    "from sklearn.model_selection import GridSearchCV, StratifiedKFold\n",
    "from sklearn.metrics import roc_auc_score, mean_absolute_error, explained_variance_score, mean_squared_error\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from collections import Counter\n",
    "import pickle\n",
    "from sklearn.pipeline import Pipeline\n",
    "from sklearn.preprocessing import LabelEncoder, OneHotEncoder\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.model_selection import cross_val_score\n",
    "from sklearn.preprocessing import StandardScaler"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__2. Первичный анализ данных__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cars = pd.read_csv('../../data/cars.csv', delimiter=';', index_col='Obs')\n",
    "cars['Invoice']=cars['Invoice'].apply(lambda x: float(x[1:].replace(',','.'))) # уберем доллар из цены\n",
    "cars['Cylinders']=cars['Cylinders'].apply(lambda x: int(x.replace('.','0'))) # для ротерных двигателей поставим 0\n",
    "y=cars['MPG_City']\n",
    "cars=cars.drop(['MPG_City', 'MPG_Highway'], axis=1) # будет не честно прогнозировать расход по городу через расход на трассе\n",
    "cars.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разобьем нашу выборку на тренировочный и тестовый набор для финальной проверки прогноза."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, X_valid, y_train, y_valid = train_test_split(cars, y, \n",
    "                     test_size=0.3, random_state=17)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__3. Первичный визуальный анализ данных__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В данных довольно много категориальных признаков, но сначала посмотрим на числовые"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set_context(\n",
    "    \"notebook\", \n",
    "    font_scale = 2,       \n",
    "    rc = { \n",
    "        \"figure.figsize\" : (12, 9), \n",
    "        \"axes.titlesize\" : 18\n",
    "    }\n",
    ")\n",
    "sns.heatmap(X_train[['Horsepower',  'Weight' , 'Wheelbase' ,'Length','EngineSize', 'Cylinders' , 'Invoice']].corr()\\\n",
    "            , annot=True, fmt='.2f')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "sns.pairplot(pd.concat([X_train[['Horsepower',  'Weight' , 'Wheelbase' ,'Length',\\\n",
    "                                 'EngineSize', 'Cylinders' , 'Invoice']], y_train], axis=1))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__4. Инсайты, найденные зависимости__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Из корреляции видно, что пары признаков\n",
    "\n",
    "Length и Wheelbase  (длина и колесная база)\n",
    "\n",
    "EngineSize и Cylinders (объем двигателя и число цилиндров)\n",
    "\n",
    "сильно коррелированы (что довольно очевидно из предметной области) и по графикам видно, что зависимость линейна до невозможности, что будет плохо для нашей модели. Можем еще посмотреть на веса (ниже).\n",
    "Также видно, что MPG_City зависит от признаков не линейно (на первый взглят квадратично) мы это потом используем для генерации новых признаков."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__5. Выбор метрики__ (совмещено с п.6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__6. Выбор модели__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Я буду прогнозировать расход топлива как непрерывный отклик, т.е. с помощью линейной регрессии. Это обусловленно природой данных и возможностью интерпретировать соответствующие метрики.\n",
    "\n",
    "Метрику возьмем довольно простую и понятную, а именно mean_absolute_error. Она нам покажет, но сколько в среднем мы \"отдаляемся\" от истиного значения.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__7. Предобработка данных__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как ни крути, а для нормальной работы модели нужно стандартизовать данные. Используем StandardScaler\n",
    "\n",
    "PCA использовать не будем. Для этого есть несколько причин. Во первых, мы уже убедились, что зависимость не оченьто и ленейна, а вовторых, потеряется интерпретируемость"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scaler=StandardScaler()\n",
    "X_train_scaled=scaler.fit_transform(X_train[['Horsepower',  'Weight' , 'Wheelbase' ,'Length'\\\n",
    "                    ,'EngineSize', 'Cylinders' , 'Invoice']])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__8. Кросс-валидация и настройка гиперпараметров модели__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "LR=LinearRegression()\n",
    "LR.fit(X_train_scaled, y_train)\n",
    "LR.coef_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что признак Cylinders имеет самый маленький вес - удалим"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "LR=LinearRegression()\n",
    "LR.fit(X_train_scaled[:,[0,1,2,3,4,6]], y_train)\n",
    "LR.coef_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "видим, что Length тоже имеет довольно малый вес даже после удаления Cylinders + он сильно коррелирован с Wheelbase, так что тоже удалим"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "вообще надо посмотреть, что мы там по удаляли"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "score=cross_val_score(LinearRegression(), X_train_scaled, y_train, scoring='mean_absolute_error')\n",
    "-np.mean(score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "score=cross_val_score(LinearRegression(), X_train_scaled[:,[0,1,2,4,6]], y_train, scoring='mean_absolute_error')\n",
    "-np.mean(score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ну.. мы удалили признаки и не сильно пострадал скор - это круто!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "label_encoder = LabelEncoder()\n",
    "label_encoder.fit(X_train['Type'])\n",
    "X_train['Type2']=label_encoder.transform(X_train['Type'])\n",
    "onehot_encoder = OneHotEncoder(sparse=False)\n",
    "onehot_encoder.fit(X_train[['Type2']])\n",
    "encoded_categorical_columns = pd.DataFrame(onehot_encoder.transform(X_train[['Type2']]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "t=pd.concat([pd.DataFrame(X_train_scaled[:,[0,1,2,4,6]], columns=['Horsepower',  'Weight' , 'Wheelbase' \\\n",
    "                    ,'EngineSize',  'Invoice']), encoded_categorical_columns], axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "LR=LinearRegression()\n",
    "LR.fit(t.drop(0, axis=1), y_train)\n",
    "LR.coef_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В общем не буду долго грузить этим отбором признаков - оставили тип Sedan и Sports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "score=cross_val_score(LinearRegression(),  t.drop([0,1,4,5], axis=1), y_train, scoring='mean_absolute_error')\n",
    "-np.mean(score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "вуаля, немного улучшили."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_scaled=t.drop([0,1,4,5], axis=1)\n",
    "X_train_scaled=X_train_scaled.rename(columns={2: label_encoder.inverse_transform(2), 3: label_encoder.inverse_transform(3)})\n",
    "X_train_scaled.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__9. Создание новых признаков и описание этого процесса __"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Помните, там выше было что-то про нелинейную зависимость из графиков. ВОТ ОНА.\n",
    "Хотя на самом деле здесь имеет влияние не столько квадраты признаков,сколько сцепленный признак Horsepower*Weight"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#X_train_scaled['Horsepower2']=X_train_scaled['Horsepower']*X_train_scaled['Horsepower']\n",
    "#X_train_scaled['Weight2']=X_train_scaled['Weight']*X_train_scaled['Weight']\n",
    "X_train_scaled['Horsepower_Weight']=X_train_scaled['Weight']*X_train_scaled['Horsepower']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "score=cross_val_score(LinearRegression(),  X_train_scaled, y_train, scoring='mean_absolute_error')\n",
    "-np.mean(score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "не плохо, правда? наша модель довольно заметно улучшилась. Ну и это понятно из природы данных - чем тяжелее машина и более мощный в ней двигатель, тем больше она сожрет топлива, особенно в городе, где приходится много стоять"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "LR=LinearRegression()\n",
    "LR.fit(X_train_scaled, y_train)\n",
    "LR.coef_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "заметим, что еще немного можно улучшить скор, убрав цену"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "score=cross_val_score(LinearRegression(),  X_train_scaled.drop('Invoice', axis=1), y_train, scoring='mean_absolute_error')\n",
    "-np.mean(score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_scaled=X_train_scaled.drop('Invoice', axis=1)\n",
    "X_train_scaled.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__10. Построение кривых валидации и обучения __"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.learning_curve import learning_curve"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_with_std(x, data, **kwargs):\n",
    "        mu, std = data.mean(1), data.std(1)\n",
    "        lines = plt.plot(x, mu, '-', **kwargs)\n",
    "        plt.fill_between(x, mu - std, mu + std, edgecolor='none',\n",
    "                         facecolor=lines[0].get_color(), alpha=0.2)\n",
    "        \n",
    "def plot_learning_curve(clf, X, y, scoring, cv=5):\n",
    " \n",
    "    train_sizes = np.linspace(0.05, 1, 20)\n",
    "    n_train, val_train, val_test = learning_curve(clf,\n",
    "                                                  X, y, train_sizes, cv=cv,\n",
    "                                                  scoring=scoring, n_jobs = -1)\n",
    "    plot_with_std(n_train, val_train, label='training scores', c='green')\n",
    "    plot_with_std(n_train, val_test, label='validation scores', c='red')\n",
    "    plt.xlabel('Training Set Size'); plt.ylabel(scoring)\n",
    "    plt.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_learning_curve(LinearRegression(),X_train_scaled, y_train, scoring='mean_absolute_error')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И тут все довольно хорошо: Кривые сходятся, \"зазор\" маленький, причем тренд идет к нулю."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__11. Прогноз для тестовой или отложенной выборке __"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_valid_scaled=scaler.transform(X_valid[['Horsepower',  'Weight' , 'Wheelbase' ,'Length'\\\n",
    "                    ,'EngineSize', 'Cylinders' , 'Invoice']])\n",
    "X_valid['Type2']=label_encoder.transform(X_valid['Type'])\n",
    "encoded_categorical_columns2 = pd.DataFrame(onehot_encoder.transform(X_valid[['Type2']]))\n",
    "t2=pd.concat([pd.DataFrame(X_valid_scaled[:,[0,1,2,4,6]], columns=['Horsepower',  'Weight' , 'Wheelbase' \\\n",
    "                    ,'EngineSize',  'Invoice']), encoded_categorical_columns2], axis=1)\n",
    "X_valid_scaled=t2.drop([0,1,4,5], axis=1)\n",
    "X_valid_scaled=X_valid_scaled.rename(columns={2: label_encoder.inverse_transform(2), 3: label_encoder.inverse_transform(3)})\n",
    "\n",
    "X_valid_scaled['Horsepower_Weight']=X_valid_scaled['Weight']*X_valid_scaled['Horsepower']\n",
    "X_valid_scaled=X_valid_scaled.drop('Invoice', axis=1)\n",
    "X_valid_scaled.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "LR=LinearRegression()\n",
    "LR.fit(X_train_scaled, y_train)\n",
    "mean_absolute_error(y_valid, LR.predict(X_valid_scaled))"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "Ну что тут можно сказать. Результат на отложенной выборке всегда хуже. В целом, ошибаться на 1-2 пункта при разбросе  переменной от 10 до 60 - довольно не плохой результат."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__12. Выводы __\n",
    "\n",
    "В результате мы получили зависимость расхода топлива от параметров автомобиля, подвердили, что есть нелинейная зависимость от этих параметров. Можно было бы еще подобавлять параметры из категориальных признаков, но,  к сожалению, у меня они только ухудшали скор. Как уже говорилось выше, данный прогноз можно применять при проектировании авто, ведь если целевая группа - бюджетный потребитель, то расход топлива - один из основных показателей при выборе авто в такой группе клиентов."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
